
#include <BTextEdit>
#include <BStyle>
#include <BPainter>
#include <BXmlDocument>
#include <BApplication>

#include "Settings.h"
#include "XmlFormat.h"

XmlFormat::XmlFormat(const BString& name) : BTextFormat(name)
{
	_style = 0;
	_textEdit = 0;

	_dockLayout = new BDockLayout();
	_dockLayout->setPart(Part_Center);
	_dockLayout->setSplitable(true);
	_dockLayout->setSpacing(5);

	//createTreeWidget();

	connect(this, Signal_AssetChanged, &XmlFormat::slotAssetChanged);

}
XmlFormat::~XmlFormat()
{

}

int XmlFormat::topItemCount() const
{
	return _topItems.size();
}
XmlItem* XmlFormat::topItem(int index)
{
	return _topItems.at(index);
}

void XmlFormat::styled(const BStyle* style)
{
	_style = style;
	_colorMap[XType_Head].set(0, 0, 128);
	_colorMap[XType_Tail].set(0, 0, 128);
	_colorMap[XType_Name].set(128, 0, 128);
	_colorMap[XType_Value].set(128, 0, 0);
	_colorMap[XType_Annotation].set(0, 128, 0);

	if (style)
	{
		if (style->hasColor("type-head"))			_colorMap[XType_Head] = style->color("type-head");
		if (style->hasColor("type-tail"))			_colorMap[XType_Tail] = style->color("type-tail");
		if (style->hasColor("type-name"))			_colorMap[XType_Name] = style->color("type-name");
		if (style->hasColor("type-value"))			_colorMap[XType_Value] = style->color("type-value");
		if (style->hasColor("type-annotation"))		_colorMap[XType_Annotation] = style->color("type-annotation");
	}
	
	if (Settings* settings = BApplication::property("settings"))
	{
		settings->assignColor("xml", "color-head", _colorMap[XType_Head]);
		settings->assignColor("xml", "color-head", _colorMap[XType_Tail]);
		settings->assignColor("xml", "color-head", _colorMap[XType_Name]);
		settings->assignColor("xml", "color-head", _colorMap[XType_Value]);
		settings->assignColor("xml", "color-annotation", _colorMap[XType_Annotation]);
	}

	this->emit(Signal_Styled, style);
}
void XmlFormat::changed(const BString& xmltext)
{
	readDocument(xmltext);
}

const BFont* XmlFormat::font(int pos) const
{
	return 0;
}
const BColor* XmlFormat::back(int pos) const
{
	return 0;
}
const BColor* XmlFormat::color(int pos) const
{
	auto it = _itemMap <= pos;
	if (XmlItem* item = it.value())
	{
		if (pos == 287)
			int a = 0;
		const BColor& color = _colorMap(item->type);
		if (pos < item->range.max() && color.a() > 0)
			return &color;
	}
	return 0;
}

void XmlFormat::createTreeWidget()
{
	_treeWidget = new BTreeWidget();
	_treeWidget->setWidthPolicy(Policy_Preferred | Policy_Expanding);
	_dockLayout->addWidget(_treeWidget, Align_RightTop);
	_dockLayout->setStretch(_treeWidget, BSizeStretch(0.1f, 0.5f));
}
void XmlFormat::freshTreeWidget()
{
	if (_treeWidget)
	{
		_treeWidget->clearItems();
		for (int i = 0; i < _topItems.size(); i++)
		{
			BTreeItem* item = createTreeItem(_topItems[i]);
			_treeWidget->addItem(item);
		}
	}
}
BTreeItem* XmlFormat::createTreeItem(XmlItem* item)
{
	const BStyle* realStyle = _treeWidget->realStyle();
	BTreeItem* treeItem = new BTreeItem(item->text);
	treeItem->setAsset(item);
	for (int i = 0; i < item->children.size(); i++)
	{
		BTreeItem* childItem = createTreeItem(item->children[i]);
		treeItem->addChild(childItem);
	}
	return treeItem;
}

XmlItem* XmlFormat::insertXmlItem(int pos, int count, XmlType type, XmlItem* parent)
{
	XmlItem* item = new XmlItem();
	item->type = type;
	item->range.min() = pos;
	item->range.max() = pos + count;
	if (parent)
	{
		item->parent = parent;
		parent->children.append(item);
	}
	_itemMap.insert(pos, item);
	return item;
}

bool XmlFormat::checkTagName(BString& head, BString& tail)
{
	return false;
}
int XmlFormat::readTagName(const BString& text, BString& tagName)
{
	if (text.empty())
		return 0;
	auto begin = text.begin();
	auto end = text.begin();
	if (text.beginWith("</"))
	{
		begin = end = text.seek(2);
	}
	else if (text.beginWith('<'))
	{
		begin = end = text.seek(1);
	}
	while (end.valid())
	{
		if (end->isSpace())
			break;
		if (end == '/')
			break;
		if (end == '>')
			break;
		end++;
	}
	tagName.reset(text, begin.index(), end.index());
	return end.count();
}
void XmlFormat::readDoctype(XmlItem* node, BString& text)
{
	node->text == text;
	text.trimmed();
	text.lower();
	if (text == "html")
		_doctype = DType_HTML;
	else
		_doctype = DType_NONE;
}

bool XmlFormat::readDocument(const BString& xmltext)
{
	_doctype = DType_NONE;
	_topItems.reset();
	_itemMap.reset();
	if (xmltext.empty())
		return false;

	BString name;
	BString text;
	auto begin = xmltext.begin();
	auto cit = xmltext.begin();
	while (cit.valid())
	{
		if (cit == '\n')
		{
			text.reset();
			begin = ++cit;
			continue;
		}
		text.append(*cit);
		if (cit == '-' && text.endWith("<!--"))
		{
			auto end = xmltext.find("-->", cit);
			XmlItem* item = insertXmlItem(cit.count() - 4, end.count() - cit.count() + 7, XType_Annotation);
			item->text = "!--";
			_topItems.append(item);
			text.reset();
			begin = cit = end + 3;
			continue;
		}
		if (cit == '\"')
		{
			auto end = xmltext.find('\"', cit);
			text.append(xmltext, cit.index(), end.index());
			cit = end;
			if (end.valid())
			{
				text.append('\"');
				cit++;
			}
			continue;
		}
		if (cit == '>')
		{
			if (text.beginWith("<!"))
			{
				int name_end = readTagName(text, name);
				XmlItem* item = insertXmlItem(cit.count() - 2, 2, XType_Head);
				item->text = name;
				text.remove(0, name_end);
				text.remove(text.size() - 1, 1);
				name.lower();
				if (name == "!doctype")
				{
					readDoctype(item, text);
				}
				_topItems.append(item);
				text.reset();
				begin = ++cit;
				continue;
			}
			if (text.beginWith("<?") && text.endWith("?>"))
			{
				int name_end = readTagName(text, name);
				XmlItem* item = insertXmlItem(begin.count(), name_end, XType_Head);
				item->text = name;
				readAttribs(item, text, begin + name_end, cit - 2);
				XmlItem* tailItem = insertXmlItem(cit.count() - 2, 2, XType_Tail, item);
				tailItem->text = "?>";
				_topItems.append(item);
				text.reset();
				begin = ++cit;
				continue;
			}
			if (text.first() == '<')
			{
				int name_end = readTagName(text, name);
				XmlItem* item = insertXmlItem(begin.count(), name_end + 1, XType_Head);
				item->text = name;
				if (text.endWith("/>"))
				{
					readAttribs(item, xmltext, begin + name_end, cit - 1);
					XmlItem* tailItem = insertXmlItem(cit.count() - 1, 2, XType_Tail, item);
					tailItem->text = "/>";
				}
				else
				{
					readAttribs(item, xmltext, begin + name_end, cit - 1);
					XmlItem* tailItem = insertXmlItem(cit.count(), 1, XType_Tail, item);
					tailItem->text = ">";
					readXmlItem(item, xmltext, ++cit);
				}
				_topItems.append(item);
				text.reset();
				begin = ++cit;
				continue;
			}
			break;
		}
		cit++;
	}

	return _topItems.size();
}
bool XmlFormat::readXmlItem(XmlItem* item, const BString& xmltext, BString::const_iterator& cit)
{
	BString text;
	auto tag_head = xmltext.begin();
	auto tag_tail = xmltext.begin();
	while (cit.valid())
	{
		text.append(*cit);
		if (cit == '\n')
		{
			cit++;
			continue;
		}
		if (cit == '-' && text.endWith("<!--"))
		{
			auto end = xmltext.find("-->", cit);
			XmlItem* item = insertXmlItem(cit.count() - 4, end.count() - cit.count() + 7, XType_Annotation);
			item->text = "!--";
			_topItems.append(item);
			text.reset();
			cit = end + 3;
			continue;
		}
		if (cit == '\"')
		{
			auto end = xmltext.find('\"', cit);
			text.append(xmltext, cit.index(), end.index());
			cit = end;
			if (end.valid())
			{
				text.append('\"');
			}
			cit++;
			continue;
		}
		if (cit == '<' && tag_tail == tag_head)
		{
			tag_head = cit;
		}
		if (cit == '>' && tag_tail < tag_head)
		{
			tag_tail = cit + 1;
		}
		if (tag_tail > tag_head)
		{
			BString tagText;
			xmltext.substr(tagText, tag_head, tag_tail);

			BString tagName;
			auto name_end = tag_head + readTagName(tagText, tagName);

			if (tagText.beginWith("</"))
			{
				if (tagName != item->text)
					return false;
				XmlItem* child = insertXmlItem(tag_head.count(), tag_tail.count() - tag_head.count(), XType_Tail, item);
				child->text = tagName;
				cit++;
				break;
			}
			if (tagText.endWith("/>"))
			{
				XmlItem* child = insertXmlItem(tag_head.count(), tagName.count() + 1, XType_Head, item);
				child->text = tagName;
				readAttribs(child, xmltext, name_end, cit - 1);
				XmlItem* tailItem = insertXmlItem(cit.count() - 1, 2, XType_Tail, child);
				tailItem->text = tagName;
				text.reset();
			}
			else
			{
				auto attrib_begin = name_end + 1;
				auto attrib_end = cit;
				if (attrib_begin < attrib_end)
				{
					XmlItem* child = insertXmlItem(tag_head.count(), name_end.count() - tag_head.count(), XType_Head, item);
					child->text = tagName;
					readAttribs(child, xmltext, attrib_begin, attrib_end);
					XmlItem* tailItem = insertXmlItem(cit.count(), 1, XType_Tail, child);
					readXmlItem(child, xmltext, ++cit);
				}
				else
				{
					XmlItem* child = insertXmlItem(tag_head.count(), name_end.count() - tag_head.count() + 1, XType_Head, item);
					child->text = tagName;
					readXmlItem(child, xmltext, ++cit);
				}
			}
			text.reset();
			tag_head = xmltext.begin();
			tag_tail = xmltext.begin();
			continue;
		}
		cit++;
	}

	return true;
}
void XmlFormat::readAttribs(XmlItem* item, const BString& xmltext, const BString::const_iterator& begin, const BString::const_iterator& end)
{
	BString attribText = xmltext.substr(begin.index(), end.index());
	BString label;
	BString value;
	auto name_begin = begin;
	auto equal = xmltext.end();
	auto it = begin;
	while (it < end)
	{
		if (it == '=')
		{
			equal = it;
		}
		if (it == '\"')
		{
			auto value_begin = it;
			it = xmltext.find('\"', it + 1, end);
			if (it >= end)
				return;
			xmltext.substr(label, name_begin, equal);
			xmltext.substr(value, value_begin + 1, it);

			XmlItem* nameItem = insertXmlItem(name_begin.count(), equal.count() - name_begin.count(), XType_Name, item);
			nameItem->text = label;

			XmlItem* valueItem = insertXmlItem(value_begin.count(), it.count() - value_begin.count() + 1, XType_Value, item);
			valueItem->text = value;

			name_begin = ++it;
			equal = xmltext.end();
			continue;
		}
		if (name_begin->isSpace() && !it->isSpace())
		{
			name_begin = it;
		}
		++it;
	}
}

void XmlFormat::slotAssetChanged(BObject* object, const BValue& value)
{
	BTextEdit* textEdit = value;
	if (_textEdit != textEdit)
	{
		if (_textEdit)
		{
			disconnect(_textEdit, Signal_Framed);
		}
		_textEdit = textEdit;
		if (_textEdit)
		{
			//connect(_textEdit, Signal_Framed, &XmlFormat::slotTextEditFramed);
		}
	}
}
void XmlFormat::slotTextEditFramed(BTextEdit* textEdit, const BValue& value)
{
	if (_textEdit)
	{
		const BIntArray& lines = textEdit->query("visual-lines");
		if (lines.size())
		{
			BPainter painter(textEdit);
			painter.setColor(Color_Text);

			BRange firstRange = textEdit->lineRange(lines.first());
			BRange lastRange = textEdit->lineRange(lines.last());
			auto begin = _itemMap >= firstRange.min();
			auto end = _itemMap > lastRange.max();
			if (begin.valid() && begin < end)
			{
				for (auto it = begin; it < end; it++)
				{
					XmlItem* item = *it;
					BRect rect = textEdit->codeRect(it.key());
					for (int i = item->range.min(); i < item->range.max(); i++)
					{
						BRect codeRect = textEdit->codeRect(i);
						rect.expand(codeRect);
					}
					painter.drawRect(rect);
				}
			}
		}
	}
}

